﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;

namespace StarUtils.Extension
{

    public static class Reflection
    {
        private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertiesDic = new ConcurrentDictionary<Type, PropertyInfo[]>();

        public static string GetDescription<T>()
        {
            return GetDescription(Common.GetType<T>());
        }

        public static string GetDescription<T>(string memberName)
        {
            return GetDescription(Common.GetType<T>(), memberName);
        }

        public static string GetDescription(Type type, string memberName)
        {
            if (type == null)
            {
                return string.Empty;
            }

            if (!memberName.IsEmpty())
            {
                return GetDescription(type.GetTypeInfo().GetMember(memberName).FirstOrDefault());
            }

            return string.Empty;
        }

        public static string GetDescription(MemberInfo member)
        {
            if (member == null)
            {
                return string.Empty;
            }

            DescriptionAttribute customAttribute = member.GetCustomAttribute<DescriptionAttribute>();
            if (customAttribute == null)
            {
                return member.Name;
            }

            return customAttribute.Description;
        }

        public static string GetDisplayName<T>()
        {
            return GetDisplayName(Common.GetType<T>());
        }

        private static string GetDisplayName(MemberInfo member)
        {
            if (member == null)
            {
                return string.Empty;
            }

            DisplayAttribute customAttribute = member.GetCustomAttribute<DisplayAttribute>();
            if (customAttribute != null)
            {
                return customAttribute.Description;
            }

            DisplayNameAttribute customAttribute2 = member.GetCustomAttribute<DisplayNameAttribute>();
            if (customAttribute2 != null)
            {
                return customAttribute2.DisplayName;
            }

            return string.Empty;
        }

        public static string GetDisplayNameOrDescription<T>()
        {
            return GetDisplayNameOrDescription(Common.GetType<T>());
        }

        public static string GetDisplayNameOrDescription(MemberInfo member)
        {
            string displayName = GetDisplayName(member);
            if (!string.IsNullOrWhiteSpace(displayName))
            {
                return displayName;
            }

            return GetDescription(member);
        }

        public static List<TInterface> GetInstancesByInterface<TInterface>(Assembly assembly)
        {
            Type typeInterface = typeof(TInterface);
            return (from t in assembly.GetTypes()
                    where typeInterface.GetTypeInfo().IsAssignableFrom(t) && t != typeInterface && !t.GetTypeInfo().IsAbstract
                    select CreateInstance<TInterface>(t, Array.Empty<object>())).ToList();
        }

        public static T CreateInstance<T>(Type type, params object[] parameters)
        {
            return Conv.To<T>(Activator.CreateInstance(type, parameters));
        }

        public static T CreateInstance<T>(string className, params object[] parameters)
        {
            return CreateInstance<T>(Type.GetType(className) ?? Assembly.GetCallingAssembly().GetType(className), parameters);
        }

        public static Assembly GetAssembly(string assemblyName)
        {
            return Assembly.Load(new AssemblyName(assemblyName));
        }

        public static List<Assembly> GetAssemblies(string directoryPath)
        {
            return (from t in Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories).ToList()
                    where t.EndsWith(".exe") || t.EndsWith(".dll")
                    select t into path
                    select Assembly.Load(new AssemblyName(path))).ToList();
        }

        public static TAttribute GetAttribute<TAttribute>(MemberInfo memberInfo) where TAttribute : Attribute
        {
            return (TAttribute)memberInfo.GetCustomAttributes(typeof(TAttribute), inherit: false).FirstOrDefault();
        }

        public static TAttribute[] GetAttributes<TAttribute>(MemberInfo memberInfo) where TAttribute : Attribute
        {
            return Array.ConvertAll(memberInfo.GetCustomAttributes(typeof(TAttribute), inherit: false), (object x) => (TAttribute)x);
        }

        public static bool IsBool(MemberInfo member)
        {
            if (member == null)
            {
                return false;
            }

            return member.MemberType switch
            {
                MemberTypes.TypeInfo => member.ToString() == "System.Boolean",
                MemberTypes.Property => IsBool((PropertyInfo)member),
                _ => false,
            };
        }

        public static bool IsBool(PropertyInfo property)
        {
            if (!(property.PropertyType == typeof(bool)))
            {
                return property.PropertyType == typeof(bool?);
            }

            return true;
        }

        public static bool IsEnum(MemberInfo member)
        {
            if (member == null)
            {
                return false;
            }

            return member.MemberType switch
            {
                MemberTypes.TypeInfo => ((TypeInfo)member).IsEnum,
                MemberTypes.Property => IsEnum((PropertyInfo)member),
                _ => false,
            };
        }

        public static bool IsEnum(PropertyInfo property)
        {
            if (property.PropertyType.GetTypeInfo().IsEnum)
            {
                return true;
            }

            Type underlyingType = Nullable.GetUnderlyingType(property.PropertyType);
            if (underlyingType == null)
            {
                return false;
            }

            return underlyingType.GetTypeInfo().IsEnum;
        }

        public static bool IsDate(MemberInfo member)
        {
            if (member == null)
            {
                return false;
            }

            return member.MemberType switch
            {
                MemberTypes.TypeInfo => member.ToString() == "System.DateTime",
                MemberTypes.Property => IsDate((PropertyInfo)member),
                _ => false,
            };
        }

        public static bool IsDate(PropertyInfo property)
        {
            if (property.PropertyType == typeof(DateTime))
            {
                return true;
            }

            if (property.PropertyType == typeof(DateTime?))
            {
                return true;
            }

            return false;
        }

        public static bool IsInt(MemberInfo member)
        {
            if (member == null)
            {
                return false;
            }

            switch (member.MemberType)
            {
                case MemberTypes.TypeInfo:
                    if (!(member.ToString() == "System.Int32") && !(member.ToString() == "System.Int16"))
                    {
                        return member.ToString() == "System.Int64";
                    }

                    return true;
                case MemberTypes.Property:
                    return IsInt((PropertyInfo)member);
                default:
                    return false;
            }
        }

        public static bool IsInt(PropertyInfo property)
        {
            if (property.PropertyType == typeof(int))
            {
                return true;
            }

            if (property.PropertyType == typeof(int?))
            {
                return true;
            }

            if (property.PropertyType == typeof(short))
            {
                return true;
            }

            if (property.PropertyType == typeof(short?))
            {
                return true;
            }

            if (property.PropertyType == typeof(long))
            {
                return true;
            }

            if (property.PropertyType == typeof(long?))
            {
                return true;
            }

            return false;
        }

        public static bool IsNumber(MemberInfo member)
        {
            if (member == null)
            {
                return false;
            }

            if (IsInt(member))
            {
                return true;
            }

            switch (member.MemberType)
            {
                case MemberTypes.TypeInfo:
                    if (!(member.ToString() == "System.Double") && !(member.ToString() == "System.Decimal"))
                    {
                        return member.ToString() == "System.Single";
                    }

                    return true;
                case MemberTypes.Property:
                    return IsNumber((PropertyInfo)member);
                default:
                    return false;
            }
        }

        public static bool IsNumber(PropertyInfo property)
        {
            if (property.PropertyType == typeof(double))
            {
                return true;
            }

            if (property.PropertyType == typeof(double?))
            {
                return true;
            }

            if (property.PropertyType == typeof(decimal))
            {
                return true;
            }

            if (property.PropertyType == typeof(decimal?))
            {
                return true;
            }

            if (property.PropertyType == typeof(float))
            {
                return true;
            }

            if (property.PropertyType == typeof(float?))
            {
                return true;
            }

            return false;
        }

        public static bool IsCollection(Type type)
        {
            if (type.IsArray)
            {
                return true;
            }

            return IsGenericCollection(type);
        }

        public static bool IsGenericCollection(Type type)
        {
            if (!type.IsGenericType)
            {
                return false;
            }

            Type genericTypeDefinition = type.GetGenericTypeDefinition();
            if (!(genericTypeDefinition == typeof(IEnumerable<>)) && !(genericTypeDefinition == typeof(IReadOnlyCollection<>)) && !(genericTypeDefinition == typeof(IReadOnlyList<>)) && !(genericTypeDefinition == typeof(ICollection<>)) && !(genericTypeDefinition == typeof(IList<>)))
            {
                return genericTypeDefinition == typeof(List<>);
            }

            return true;
        }

        public static List<JsonItem> GetPublicProperties(object instance)
        {
            return (from t in instance.GetType().GetProperties().ToList()
                    select new JsonItem(t.Name, t.GetValue(instance))).ToList();
        }

        public static PropertyInfo[] GetEntityProperties(Type type)
        {
            return PropertiesDic.GetOrAdd(type, (Type item) => type.GetProperties(BindingFlags.Instance | BindingFlags.Public));
        }
    }
}
